#include <linux/module.h>

#include "testing.h"
#include "context.h"
#include "paging_api.h"

#define info(...) printk("info " __VA_ARGS__)

void run_until_result (void);
void handle_addr_cmd (struct test_command *c);
struct test_command * get_current_command (void);
void restore_data (void);
void handle (struct test_command *c);
typedef void (*callback)(struct test_command *);
void do_until_result (struct test_command *c, callback func);

struct tester_data TESTER;

int n;


void print_int_info_header(void)
{
  info("%-10s%6s%9s%11s%6s%6s\n",
       "test",
       "int?",
       "int num",
       "err code?",
       "Tcmd",
       "n"
      );
}



void print_int_info(char *name, struct test_result *res)
{
  info("%-10s%6s%9hu%11s%6d\n",
       name,
       (res->int_info.interrupt ? "yes" : "no"),
       res->int_info.int_num,
       (res->int_info.has_error_code ? "yes" : "no"),
       n
      );
}


void *idt_page;
u64 *pml4;
struct test_page pages[10];

  u64 *hmm2;
void init_contexts(void) {
  save_context (&(TESTER.linux_context));

  pages[0].info.offset_mask = OFFSET_MASK_4KB;
  pages[0].info.in_memory = 1;
  pages[0].virt = (u64 *) get_zeroed_page (GFP_DMA);
  pages[0].phys = virt_to_phys(pages[0].virt);
  
  pml4 = pages[0].virt;
  pml4_map_kernel(pml4);
  
  idt_page = (void *) get_zeroed_page (GFP_DMA);
  init_test_idt64(idt_page);
  
  TESTER.test_context.idtr = make_test_idtr(idt_page);
  
  TESTER.test_context.cr3 = (TESTER.linux_context.cr3 & 0xfff) | virt_to_phys(pml4);
}



char glob;

asm("hmm:\n\t"
    "int $5\n\t"
);


char *command_name[50];
void *temp_page;
void init_tester(void) {
  int i;
  temp_page = (void *) get_zeroed_page (GFP_KERNEL);
  temp_init(&TESTER.tmp, temp_page);
  TESTER.pages = pages;
  TESTER.results = (void *) get_zeroed_page (GFP_KERNEL);
  TESTER.result = TESTER.results;
  
  for (i=1; i<10; ++i) {
    pages[i].info.offset_mask = OFFSET_MASK_4KB;
    pages[i].info.in_memory = 1;
    pages[i].virt = (u64 *) get_zeroed_page (GFP_KERNEL);
    pages[i].phys = virt_to_phys(pages[i].virt);
  }
    
}


inline int f(int x) {
  return x+100;
}

struct test_command commands[50];

void comm(int ind, char *name, u32 cmd, u64 vaddr, u32 page) {
  commands[ind].page = page;
  commands[ind].has_result = 1;
  commands[ind].cmd = cmd;
  commands[ind].vaddr = vaddr;
  
  command_name[ind] = name;
}

void map(int ind, u32 page, u32 pos, u32 page2) {
  commands[ind].page = page;
  commands[ind].page2 = page2;
  commands[ind].pos = pos;
  commands[ind].addr_mask = ADDR_MASK_64_4KB;
  
  commands[ind].has_result = 0;
  commands[ind].cmd = CMD_MAP;
}

void flags(int ind, u32 page, u32 pos, u64 flags) {
  commands[ind].page = page;
  commands[ind].pos = pos;
  commands[ind].flags = flags;
  commands[ind].uflags = 0;
  
  commands[ind].has_result = 0;
  commands[ind].cmd = CMD_FLAGS;
}

void uflags(int ind, u32 page, u32 pos, u64 uflags) {
  commands[ind].page = page;
  commands[ind].pos = pos;
  commands[ind].flags = 0;
  commands[ind].uflags = uflags;
  
  commands[ind].has_result = 0;
  commands[ind].cmd = CMD_FLAGS;
}

void set_comm(void) {
  u64 hmm_addr;
  asm volatile("movq $hmm, %0" : "=r" (hmm_addr));
  
  TESTER.cmd_ind = 0;
  TESTER.commands = commands;
  
  //PML4E -> PDPTE
  map   (0, 0, 1, 1);
  flags (1, 0, 1, PF_PRESENT);
  
  //PDPTE -> PDE
  map   (2, 1, 1, 2);
  flags (3, 1, 1, PF_PRESENT);
  
  //PDE -> PTE
  map   (4, 2, 1, 3);
  flags (5, 2, 1, PF_PRESENT);
  
  //PTE -> 4kb page
  map   (6, 3, 1, 4);
  flags (7, 3, 1, PF_PRESENT);
  
  comm  (8, "read 1.1.1.1.17", CMD_READ, vaddr64_5(1,1,1,1,17), 4);
  
  uflags (9, 3, 1, PF_PRESENT);
  
  comm  (10, "inv 1.1.1.1.17", CMD_INV, vaddr64_5(1,1,1,1,17), 4);
  comm  (11, "read 1.1.1.1.17", CMD_READ, vaddr64_5(1,1,1,1,17), 4);
   
  comm  (12, "flush", CMD_FLUSH, 0, 0);
  comm  (13, "end", CMD_END, 0, 0);
}

void send_to_host(const void *buf, int count) {
  ;//print_int_info(command_name[n++]);
};


void free_memory(void) {
  int i;
  //dont free pml4!! its == pages[0].virt
  free_page((u64)idt_page);
  free_page((u64)temp_page);
  free_page((u64)TESTER.results);
  
  for (i=0; i<10; ++i)
    free_page((u64) pages[i].virt);
}

void print_results(void) {
  struct test_result *t;
  char name[] = "0 ";
  
  for (t = TESTER.results; t < TESTER.result; ++t) {
    print_int_info (name, t);
    name[0] ++;
  }
}

static int my_init(void)
{
  info("begin\n");
  init_contexts();
  init_tester();
  set_comm();
  
  info("contexts initialized\n");
  print_int_info_header();
  
  run_test();
  print_results();
  
  free_memory();
  
  info("glob: %d\n", (int)glob);
  info("finish\n");
  
  return 0;
}

static void my_exit(void) {
    info("exit\n");
}

module_init(my_init);
module_exit(my_exit);
